ποΈGitΠ―ΡΠ°ποΈ
Commit ece771edb06f0110050baac322a093214b89f6c8
Parents : bbdc4a3
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-05-19T09:06:12-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-05-19T09:06:12-05:00
docs: comprehensive accuracy audit and CI fix (#5489)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Changes
28 files changed, 247 insertions(+), 185 deletions(-)
Diff
diff --git a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt
index 450b05e3be..15cbdca9ee 100644
--- a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt
+++ b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt
@@ -61,7 +61,7 @@ class DocsTasks : Plugin<Project> {
bundleDir.set(outputDir.map { it.dir("common") })
schemaFile.set(
project.rootProject.layout.projectDirectory
- .file("specs/003-app-docs-markdown/contracts/keyword-index-schema.json")
+ .file("specs/20260507-161858-app-docs-markdown/contracts/keyword-index-schema.json")
)
}
diff --git a/docs/_config.yml b/docs/_config.yml
index 4b76bae58f..bd7c6dc009 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -28,12 +28,12 @@ color_scheme: meshtastic
# Default front-matter for pages in subdirectories
defaults:
- scope:
- path: "user"
+ path: "user/"
values:
parent: User Guide
layout: default
- scope:
- path: "developer"
+ path: "developer/"
values:
parent: Developer Guide
layout: default
@@ -41,229 +41,229 @@ defaults:
# They use a dedicated locale layout with a back-link to the English version.
# Auto-generated from Android app locales (values-* resource dirs).
- scope:
- path: "ar"
+ path: "ar/"
values:
layout: locale_page
locale: ar
nav_exclude: true
- scope:
- path: "be"
+ path: "be/"
values:
layout: locale_page
locale: be
nav_exclude: true
- scope:
- path: "bg"
+ path: "bg/"
values:
layout: locale_page
locale: bg
nav_exclude: true
- scope:
- path: "ca"
+ path: "ca/"
values:
layout: locale_page
locale: ca
nav_exclude: true
- scope:
- path: "cs"
+ path: "cs/"
values:
layout: locale_page
locale: cs
nav_exclude: true
- scope:
- path: "de"
+ path: "de/"
values:
layout: locale_page
locale: de
nav_exclude: true
- scope:
- path: "el"
+ path: "el/"
values:
layout: locale_page
locale: el
nav_exclude: true
- scope:
- path: "es"
+ path: "es/"
values:
layout: locale_page
locale: es
nav_exclude: true
- scope:
- path: "et"
+ path: "et/"
values:
layout: locale_page
locale: et
nav_exclude: true
- scope:
- path: "fi"
+ path: "fi/"
values:
layout: locale_page
locale: fi
nav_exclude: true
- scope:
- path: "fr"
+ path: "fr/"
values:
layout: locale_page
locale: fr
nav_exclude: true
- scope:
- path: "ga"
+ path: "ga/"
values:
layout: locale_page
locale: ga
nav_exclude: true
- scope:
- path: "gl"
+ path: "gl/"
values:
layout: locale_page
locale: gl
nav_exclude: true
- scope:
- path: "he"
+ path: "he/"
values:
layout: locale_page
locale: he
nav_exclude: true
- scope:
- path: "hr"
+ path: "hr/"
values:
layout: locale_page
locale: hr
nav_exclude: true
- scope:
- path: "ht"
+ path: "ht/"
values:
layout: locale_page
locale: ht
nav_exclude: true
- scope:
- path: "hu"
+ path: "hu/"
values:
layout: locale_page
locale: hu
nav_exclude: true
- scope:
- path: "is"
+ path: "is/"
values:
layout: locale_page
locale: is
nav_exclude: true
- scope:
- path: "it"
+ path: "it/"
values:
layout: locale_page
locale: it
nav_exclude: true
- scope:
- path: "ja"
+ path: "ja/"
values:
layout: locale_page
locale: ja
nav_exclude: true
- scope:
- path: "ko"
+ path: "ko/"
values:
layout: locale_page
locale: ko
nav_exclude: true
- scope:
- path: "lt"
+ path: "lt/"
values:
layout: locale_page
locale: lt
nav_exclude: true
- scope:
- path: "nl"
+ path: "nl/"
values:
layout: locale_page
locale: nl
nav_exclude: true
- scope:
- path: "no"
+ path: "no/"
values:
layout: locale_page
locale: no
nav_exclude: true
- scope:
- path: "pl"
+ path: "pl/"
values:
layout: locale_page
locale: pl
nav_exclude: true
- scope:
- path: "pt"
+ path: "pt/"
values:
layout: locale_page
locale: pt
nav_exclude: true
- scope:
- path: "pt-rBR"
+ path: "pt-rBR/"
values:
layout: locale_page
locale: pt-rBR
nav_exclude: true
- scope:
- path: "ro"
+ path: "ro/"
values:
layout: locale_page
locale: ro
nav_exclude: true
- scope:
- path: "ru"
+ path: "ru/"
values:
layout: locale_page
locale: ru
nav_exclude: true
- scope:
- path: "sk"
+ path: "sk/"
values:
layout: locale_page
locale: sk
nav_exclude: true
- scope:
- path: "sl"
+ path: "sl/"
values:
layout: locale_page
locale: sl
nav_exclude: true
- scope:
- path: "sq"
+ path: "sq/"
values:
layout: locale_page
locale: sq
nav_exclude: true
- scope:
- path: "sr"
+ path: "sr/"
values:
layout: locale_page
locale: sr
nav_exclude: true
- scope:
- path: "sv"
+ path: "sv/"
values:
layout: locale_page
locale: sv
nav_exclude: true
- scope:
- path: "tr"
+ path: "tr/"
values:
layout: locale_page
locale: tr
nav_exclude: true
- scope:
- path: "uk"
+ path: "uk/"
values:
layout: locale_page
locale: uk
nav_exclude: true
- scope:
- path: "zh-rCN"
+ path: "zh-rCN/"
values:
layout: locale_page
locale: zh-rCN
nav_exclude: true
- scope:
- path: "zh-rTW"
+ path: "zh-rTW/"
values:
layout: locale_page
locale: zh-rTW
diff --git a/docs/_includes/language_switcher.html b/docs/_includes/language_switcher.html
index 1b0cf42380..3fe3bb662d 100644
--- a/docs/_includes/language_switcher.html
+++ b/docs/_includes/language_switcher.html
@@ -15,44 +15,56 @@
{% assign locales = site.data.locales %}
{% if locales and current_path %}
+{% assign path_parts = current_path | split: "/" %}
+{% assign first_segment = path_parts[0] %}
+
+{% comment %} Build the list of available translations first {% endcomment %}
+{% assign has_translations = false %}
+
+{% if locales[first_segment] %}
+ {% comment %} We're on a translated page β English link is always available {% endcomment %}
+ {% assign has_translations = true %}
+ {% assign remaining_parts = path_parts | slice: 1, path_parts.size %}
+ {% assign en_path = remaining_parts | join: "/" | replace: ".md", "" %}
+{% else %}
+ {% comment %} Check if any translated version exists {% endcomment %}
+ {% assign en_relative = current_path | replace: ".md", "" %}
+ {% for locale in locales %}
+ {% assign locale_code = locale[0] %}
+ {% assign locale_file = locale_code | append: "/" | append: en_relative | append: ".md" %}
+ {% for p in site.pages %}
+ {% if p.path == locale_file %}
+ {% assign has_translations = true %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+ {% if has_translations %}{% break %}{% endif %}
+ {% endfor %}
+{% endif %}
+
+{% if has_translations %}
<details class="language-switcher" aria-label="Language options">
<summary class="language-switcher-btn" title="View in another language">
π <span class="lang-current">English</span>
</summary>
<ul class="language-switcher-list">
- {% comment %} Always show English link back to source {% endcomment %}
- {% assign path_parts = current_path | split: "/" %}
- {% assign first_segment = path_parts[0] %}
-
- {% comment %} Detect if we're currently IN a locale subdir {% endcomment %}
{% if locales[first_segment] %}
- {% comment %} We're on a translated page β link back to English {% endcomment %}
- {% assign remaining_parts = path_parts | slice: 1, path_parts.size %}
- {% assign en_path = remaining_parts | join: "/" | replace: ".md", "" %}
<li><a href="{{ en_path | relative_url }}" lang="en">English</a></li>
{% endif %}
- {% comment %} Show all available locale versions {% endcomment %}
{% for locale in locales %}
{% assign locale_code = locale[0] %}
{% assign locale_info = locale[1] %}
{% if locales[first_segment] %}
- {% comment %} We're already on a translated page {% endcomment %}
{% if locale_code == first_segment %}
{% continue %}
{% endif %}
{% assign locale_path = locale_code | append: "/" | append: en_path %}
{% else %}
- {% comment %} We're on an English page {% endcomment %}
- {% assign en_relative = current_path | replace: ".md", "" %}
{% assign locale_path = locale_code | append: "/" | append: en_relative %}
{% endif %}
- {% comment %}
- Check if the translated file actually exists.
- Jekyll doesn't have a file_exists filter, so we check site.pages.
- {% endcomment %}
{% assign locale_file = locale_path | append: ".md" %}
{% assign page_exists = false %}
{% for p in site.pages %}
@@ -69,3 +81,4 @@
</ul>
</details>
{% endif %}
+{% endif %}
diff --git a/docs/assets/screenshots/README.md b/docs/assets/screenshots/README.md
index 5fb2efe281..edf5ca8d1c 100644
--- a/docs/assets/screenshots/README.md
+++ b/docs/assets/screenshots/README.md
@@ -30,7 +30,6 @@ Then copy the relevant light-mode PNGs from the reference directory. The
Examples:
- `onboarding_welcome.png`
- `connections_bluetooth_scan.png`
-- `messages-and-channels_channel_list.png`
- `firmware_disclaimer.png`
## Guidelines
diff --git a/docs/assets/screenshots/messages-and-channels_channel_list.png b/docs/assets/screenshots/messages-and-channels_channel_list.png
deleted file mode 100644
index 4054990a14..0000000000
Binary files a/docs/assets/screenshots/messages-and-channels_channel_list.png and /dev/null differ
diff --git a/docs/assets/screenshots/messages_channel_info.png b/docs/assets/screenshots/messages_channel_info.png
deleted file mode 100644
index 6f022378f8..0000000000
Binary files a/docs/assets/screenshots/messages_channel_info.png and /dev/null differ
diff --git a/docs/assets/screenshots/settings-radio-user_lora_config.png b/docs/assets/screenshots/settings-radio-user_lora_config.png
deleted file mode 100644
index aecd2b6208..0000000000
Binary files a/docs/assets/screenshots/settings-radio-user_lora_config.png and /dev/null differ
diff --git a/docs/developer.md b/docs/developer.md
index 3802081cd0..19785e5c73 100644
--- a/docs/developer.md
+++ b/docs/developer.md
@@ -3,7 +3,6 @@ title: Developer Guide
layout: default
nav_order: 2
has_children: true
-parent: ""
---
# Developer Guide
diff --git a/docs/developer/adding-a-feature-module.md b/docs/developer/adding-a-feature-module.md
index e2bc3634dc..ae290dbda2 100644
--- a/docs/developer/adding-a-feature-module.md
+++ b/docs/developer/adding-a-feature-module.md
@@ -23,15 +23,10 @@ mkdir -p feature/my-feature/src/{commonMain,commonTest,androidMain,jvmMain,iosMa
```kotlin
plugins {
alias(libs.plugins.meshtastic.kmp.feature)
- alias(libs.plugins.meshtastic.kotlinx.serialization)
- id("meshtastic.kmp.jvm.android")
}
kotlin {
- android {
- namespace = "org.meshtastic.feature.myfeature"
- androidResources.enable = false
- }
+ androidLibrary { withHostTest { } }
sourceSets {
commonMain.dependencies {
@@ -82,8 +77,8 @@ class FeatureMyFeatureModule
## 5. Register DI in App/Desktop
Add your module to:
-- `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`
-- `desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt`
+- `androidApp/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`
+- `desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt`
## 6. Add Navigation Routes
diff --git a/docs/developer/architecture.md b/docs/developer/architecture.md
index c3d5395ff2..8d1bacc1b3 100644
--- a/docs/developer/architecture.md
+++ b/docs/developer/architecture.md
@@ -16,7 +16,7 @@ The Meshtastic Android/Desktop/iOS application follows a modular Kotlin Multipla
```
βββββββββββββββββββββββββββββββββββββββββββββββ
-β app / desktop β Platform entry points
+β androidApp / desktopApp β Platform entry points
βββββββββββββββββββββββββββββββββββββββββββββββ€
β feature/* modules β UI + Business Logic
βββββββββββββββββββββββββββββββββββββββββββββββ€
@@ -28,7 +28,7 @@ The Meshtastic Android/Desktop/iOS application follows a modular Kotlin Multipla
## Module Categories
-### `app/` β Android Application
+### `androidApp/` β Android Application
The Android application entry point:
- Activity, Application, and Manifest definitions
@@ -36,13 +36,13 @@ The Android application entry point:
- Flavor-specific bindings (`google/`, `fdroid/`)
- Android-only integrations (widgets, services)
-### `desktop/` β Desktop JVM Application
+### `desktopApp/` β Desktop JVM Application
The Desktop (Linux/macOS/Windows) entry point:
- Compose Desktop window management
- Desktop-specific DI (`DesktopKoinModule`)
- Platform stubs for Android-only capabilities
-- Serial transport implementation
+- BLE (Kable), Serial, and TCP transport implementations
### `feature/*` β Feature Modules
diff --git a/docs/developer/codebase.md b/docs/developer/codebase.md
index 045223dfa6..9f67c6e97a 100644
--- a/docs/developer/codebase.md
+++ b/docs/developer/codebase.md
@@ -16,11 +16,11 @@ Repository layout, namespacing conventions, and build system overview.
```
Meshtastic-Android/
-βββ app/ # Android application module
+βββ androidApp/ # Android application module
β βββ src/main/ # Shared Android code
β βββ src/google/ # Google Play flavor (Gemini, proprietary)
β βββ src/fdroid/ # F-Droid flavor (FOSS-only)
-βββ desktop/ # Desktop JVM application
+βββ desktopApp/ # Desktop JVM application
βββ feature/ # Feature modules (KMP)
β βββ intro/
β βββ messaging/
@@ -33,22 +33,27 @@ Meshtastic-Android/
β βββ wifi-provision/
β βββ widget/
βββ core/ # Core infrastructure modules (KMP)
+β βββ api/
+β βββ barcode/
+β βββ ble/
β βββ common/
-β βββ navigation/
-β βββ ui/
-β βββ resources/
-β βββ model/
β βββ data/
β βββ database/
β βββ datastore/
-β βββ prefs/
-β βββ repository/
-β βββ service/
β βββ di/
+β βββ domain/
+β βββ model/
+β βββ navigation/
β βββ network/
-β βββ ble/
+β βββ nfc/
+β βββ prefs/
β βββ proto/
-β βββ testing/
+β βββ repository/
+β βββ resources/
+β βββ service/
+β βββ takserver/
+β βββ testing/
+β βββ ui/
βββ build-logic/ # Convention plugins and build helpers
β βββ convention/
βββ docs/ # Documentation source (markdown)
@@ -115,7 +120,7 @@ Located in `build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/`:
./gradlew assembleGoogleDebug assembleFdroidDebug
# Desktop run
-./gradlew :desktop:run
+./gradlew :desktopApp:run
```
## Version Catalog Highlights
diff --git a/docs/developer/contributing.md b/docs/developer/contributing.md
index 7783fac1af..f5d0bbaf65 100644
--- a/docs/developer/contributing.md
+++ b/docs/developer/contributing.md
@@ -14,15 +14,26 @@ Guidelines for contributing to the Meshtastic Android/Desktop/iOS project.
## Branch Naming
-Feature branches follow the pattern:
-```
-{issue-number}-{short-description}
-```
+Branches use conventional-commit style prefixes:
+
+| Prefix | Use for |
+|--------|---------|
+| `feat/<scope>` | New user-visible behavior |
+| `fix/<scope>` | Bug fixes |
+| `refactor/<scope>` | Code structure changes |
+| `chore/<scope>` | Tooling, deps, CI, cleanup |
+| `docs/<scope>` | Documentation only |
+| `build/<scope>` | Build system changes |
+| `ci/<scope>` | CI workflow changes |
+| `test/<scope>` | Test additions or fixes |
+| `deps/<scope>` | Dependency updates |
+
+Numeric spec prefixes (e.g., `003-app-docs-markdown`) are also valid for spec-driven work.
Examples:
-- `003-app-docs-markdown`
-- `001-local-mesh-discovery`
+- `feat/desktop-ble-transport`
- `fix/bluetooth-reconnect`
+- `003-app-docs-markdown`
## Development Workflow
diff --git a/docs/developer/navigation-and-deep-links.md b/docs/developer/navigation-and-deep-links.md
index 08622c60a6..c86afdf852 100644
--- a/docs/developer/navigation-and-deep-links.md
+++ b/docs/developer/navigation-and-deep-links.md
@@ -24,7 +24,7 @@ interface Graph : Route // Graph roots for navigation hierarchies
@Serializable
sealed interface SettingsRoute : Route {
- @Serializable data class SettingsGraph(val destNum: Int?) : SettingsRoute, Graph
+ @Serializable data class Settings(val destNum: Int? = null) : SettingsRoute, Graph
@Serializable data object DeviceConfiguration : SettingsRoute
@Serializable data object HelpDocs : SettingsRoute
@Serializable data class HelpDocPage(val pageId: String) : SettingsRoute
@@ -54,7 +54,7 @@ meshtastic://meshtastic/{path}
| URI Path | Route | Notes |
|----------|-------|-------|
-| `/settings` | `SettingsRoute.SettingsGraph(null)` | Settings root |
+| `/settings` | `SettingsRoute.Settings(null)` | Settings root |
| `/settings/helpDocs` | `SettingsRoute.HelpDocs` | Docs browser |
| `/settings/helpDocs/{pageId}` | `SettingsRoute.HelpDocPage(pageId)` | Specific doc page |
| `/settings/help-docs` | `SettingsRoute.HelpDocs` | Compatibility alias |
@@ -70,7 +70,7 @@ Deep links synthesize a full backstack, not just the target screen:
```kotlin
// /settings/helpDocs/messages-and-channels produces:
listOf(
- SettingsRoute.SettingsGraph(null),
+ SettingsRoute.Settings(null),
SettingsRoute.HelpDocs,
SettingsRoute.HelpDocPage("messages-and-channels"),
)
@@ -90,9 +90,9 @@ This ensures the user can navigate "up" correctly.
Each feature module provides entries via an extension function:
```kotlin
-fun EntryProviderScope<*>.docsEntries(backStack: NavBackStack) {
+fun EntryProviderScope<NavKey>.docsEntries(backStack: NavBackStack<NavKey>) {
entry<SettingsRoute.HelpDocs> { DocsBrowserScreen(backStack) }
- entry<SettingsRoute.HelpDocPage> { DocsPageRouteScreen(it.pageId, backStack) }
+ entry<SettingsRoute.HelpDocPage> { route -> DocsPageRouteScreen(route.pageId, backStack) }
}
```
@@ -105,17 +105,5 @@ Deep link routing is tested in:
core/navigation/src/commonTest/kotlin/org/meshtastic/core/navigation/DeepLinkRouterTest.kt
```
-Example:
-```kotlin
-@Test
-fun `help docs deep link routes correctly`() {
- val result = DeepLinkRouter.route(CommonUri.parse("meshtastic://meshtastic/settings/helpDocs"))
- assertEquals(
- listOf(SettingsRoute.SettingsGraph(null), SettingsRoute.HelpDocs),
- result,
- )
-}
-```
-
---
diff --git a/docs/developer/persistence.md b/docs/developer/persistence.md
index 9b42538e96..4f1f1f472f 100644
--- a/docs/developer/persistence.md
+++ b/docs/developer/persistence.md
@@ -35,20 +35,30 @@ The primary structured data store:
| Entity | Description |
|--------|-------------|
-| Nodes | All known mesh nodes and metadata |
-| Messages | Message history (channel and direct) |
-| Waypoints | Shared geographic points |
-| Telemetry | Device, environment, power metrics |
-| Channels | Channel configurations |
+| `NodeEntity` | All known mesh nodes and their metadata |
+| `MyNodeEntity` | The local node's own info |
+| `Packet` | Message history (channel and direct), waypoints, and telemetry data |
+| `ContactSettings` | Per-contact mute and read-state |
+| `ReactionEntity` | Emoji reactions on messages |
+| `MeshLog` | Raw mesh protocol logs |
+| `MetadataEntity` | Device metadata (firmware version, hardware model) |
+| `QuickChatAction` | User-configured quick-chat messages |
+| `DeviceHardwareEntity` | Cached device hardware catalog |
+| `FirmwareReleaseEntity` | Cached firmware release info |
+| `TracerouteNodePositionEntity` | Traceroute hop position data |
+
+> π‘ **Note:** Waypoints, telemetry, and channel data are stored within the `Packet` entity (using the `port_num` field to distinguish packet types) rather than in separate tables.
## DataStore Preferences
**Module:** `core:datastore`
For lightweight key-value preferences:
-- Connection state
-- Last connected device
-- UI preferences
+- Local radio configuration (LocalConfig proto)
+- Module configuration (ModuleConfig proto)
+- Channel set data
+- Local statistics
+- Recently connected device addresses
## Core Prefs
diff --git a/docs/developer/testing.md b/docs/developer/testing.md
index 816ab65272..d1ed344fc5 100644
--- a/docs/developer/testing.md
+++ b/docs/developer/testing.md
@@ -55,12 +55,12 @@ Located in `commonTest` or `jvmTest` source sets.
### Screenshot Tests
-Preferred: **Roborazzi** (Gradle-native, Ubuntu CI compatible)
-Fallback: **Paparazzi** (Android-view-centric)
+Uses Android Gradle Plugin's native screenshot testing framework:
```bash
-./gradlew recordDocsScreenshots # Record golden images
-./gradlew verifyDocsScreenshots # Compare against goldens
+./gradlew :screenshot-tests:updateDebugScreenshotTest # Record golden images
+./gradlew :screenshot-tests:validateDebugScreenshotTest # Compare against goldens
+./gradlew :screenshot-tests:copyDocsScreenshots # Copy reference images to docs pipeline
```
## Test Organization
diff --git a/docs/developer/transport.md b/docs/developer/transport.md
index 15467f0ba0..82faaa2699 100644
--- a/docs/developer/transport.md
+++ b/docs/developer/transport.md
@@ -24,9 +24,9 @@ App β RadioController β Transport (BLE | Serial | TCP)
## Bluetooth Low Energy (BLE)
**Module:** `core:ble`
-**Platforms:** Android, (planned: iOS)
+**Platforms:** Android, Desktop (JVM via Kable), iOS (planned)
-The primary transport for Android mobile devices:
+The primary transport for mobile devices and also available on desktop:
- Service discovery for Meshtastic GATT services
- Characteristic-based read/write for protobuf packets
- Connection state management and automatic reconnection
@@ -35,7 +35,7 @@ The primary transport for Android mobile devices:
### Key Classes
- `core/ble/` β BLE scanning, connection, and GATT operations
-- Platform-specific implementations in `androidMain`
+- Platform-specific implementations in `androidMain` and `jvmMain` (Kable)
## USB Serial
@@ -51,7 +51,7 @@ Serial communication over USB:
### Key Classes
- Serial prober and transport factory in `core/network`
-- Desktop-specific serial in `desktop/src/main/kotlin/.../radio/`
+- Desktop-specific serial in `desktopApp/src/main/kotlin/.../radio/`
## TCP/IP
@@ -70,13 +70,17 @@ The `RadioTransportFactory` interface abstracts transport creation:
```kotlin
interface RadioTransportFactory {
- fun createTransport(config: TransportConfig): RadioTransport
+ val supportedDeviceTypes: List<DeviceType>
+ fun createTransport(address: String, service: RadioInterfaceService): RadioTransport
+ fun isMockTransport(): Boolean
+ fun isAddressValid(address: String?): Boolean
+ fun toInterfaceAddress(interfaceId: InterfaceId, rest: String): String
}
```
Platform-specific implementations:
- **Android:** Supports BLE + USB + TCP
-- **Desktop:** Supports USB + TCP (no BLE)
+- **Desktop:** Supports BLE (Kable) + USB + TCP
- **iOS:** Planned BLE + TCP
## Connection Lifecycle
diff --git a/docs/translations.md b/docs/translations.md
index 347d70d00a..feda7aaa59 100644
--- a/docs/translations.md
+++ b/docs/translations.md
@@ -10,14 +10,17 @@ This documentation is translated by the community via [Crowdin](https://crowdin.
## Available Languages
+{% assign any_locale_exists = false %}
{% for locale in site.data.locales %}
{% assign locale_code = locale[0] %}
{% assign locale_info = locale[1] %}
-{% assign locale_index = locale_code | append: "/index.md" %}
+{% assign locale_prefix = locale_code | append: "/" %}
{% assign has_content = false %}
{% for p in site.pages %}
- {% if p.path contains locale_code %}
+ {% assign page_path_check = p.path | slice: 0, locale_prefix.size %}
+ {% if page_path_check == locale_prefix %}
{% assign has_content = true %}
+ {% assign any_locale_exists = true %}
{% break %}
{% endif %}
{% endfor %}
@@ -27,19 +30,6 @@ This documentation is translated by the community via [Crowdin](https://crowdin.
{% endif %}
{% endfor %}
-{% comment %} Show notice if no translations exist yet {% endcomment %}
-{% assign any_locale_exists = false %}
-{% for locale in site.data.locales %}
- {% assign locale_code = locale[0] %}
- {% for p in site.pages %}
- {% if p.path contains locale_code %}
- {% assign any_locale_exists = true %}
- {% break %}
- {% endif %}
- {% endfor %}
- {% if any_locale_exists %}{% break %}{% endif %}
-{% endfor %}
-
{% unless any_locale_exists %}
> No translations available yet. Want to help? [Join our Crowdin project β](https://crowdin.com/project/meshtastic-android)
{% endunless %}
diff --git a/docs/user.md b/docs/user.md
index 72fcfbf5b0..1b88fac940 100644
--- a/docs/user.md
+++ b/docs/user.md
@@ -3,7 +3,6 @@ title: User Guide
layout: default
nav_order: 1
has_children: true
-parent: ""
---
# User Guide
diff --git a/docs/user/connections.md b/docs/user/connections.md
index 574433b50a..0f69adb81b 100644
--- a/docs/user/connections.md
+++ b/docs/user/connections.md
@@ -80,25 +80,36 @@ Some Meshtastic radios support WiFi connectivity, allowing TCP-based connections
3. Enter the radio's IP address and port (default: 4403).
4. Tap **Connect**.
+
+
+When a device is found, it appears in the connection list:
+
+
+
+A successful connection is confirmed with a status indicator:
+
+
+
### When to Use TCP
- Radio is on the same local network
- Testing with a simulated radio
- Environments where Bluetooth has interference issues
-## Connection Priority
+## Reconnection Behavior
+
+The app reconnects to the **last selected device** on startup. You can manually switch transports from the connections screen at any time.
+
+To disconnect from a radio, use the disconnect button on the connections screen:
-The app attempts connections in this order:
-1. Last successful Bluetooth device
-2. USB (if detected)
-3. Manual TCP (if configured)
+
## Desktop Connections
On Desktop (Linux/macOS/Windows), the app supports:
-- **USB Serial** β primary connection method
+- **Bluetooth (BLE)** β via the Kable library; works on macOS, Linux, and Windows
+- **USB Serial** β primary wired connection method
- **TCP/IP** β for network-connected radios
-- Bluetooth is **not** currently supported on Desktop
See [Desktop App](desktop) for platform-specific details and keyboard shortcuts.
diff --git a/docs/user/desktop.md b/docs/user/desktop.md
index 2734b9ab39..8921fcb3f3 100644
--- a/docs/user/desktop.md
+++ b/docs/user/desktop.md
@@ -24,7 +24,7 @@ The Desktop app shares its core codebase with the Android app through Kotlin Mul
### Linux
- Download the `.deb` or `.AppImage` package from the releases page
-- Or build from source using `./gradlew :desktop:run`
+- Or build from source using `./gradlew :desktopApp:run`
### macOS
@@ -53,9 +53,13 @@ For network-connected radios:
1. Enter the radio's IP address and port (default: 4403).
2. Click **Connect**.
-### Bluetooth
+### Bluetooth (BLE)
-> β οΈ **Note:** Bluetooth is not currently supported on the Desktop app. Use USB or TCP connections.
+Bluetooth Low Energy is supported on Desktop via the [Kable](https://github.com/JuulLabs/kable) library:
+
+1. Ensure your system has a Bluetooth adapter.
+2. The app scans for nearby Meshtastic radios automatically.
+3. Select your device from the connections screen.
## Feature Parity
@@ -65,7 +69,7 @@ For network-connected radios:
| Node List | β | β | Full parity |
| Map | β | β | Full parity |
| Settings | β | β | Full parity |
-| Bluetooth | β | β | USB/TCP on desktop |
+| Bluetooth (BLE) | β | β | Via Kable on desktop |
| Firmware Update OTA | β | β | Use web flasher |
| Notifications | β | β | Native OS notifications |
| Widgets | β | β | Android-only |
@@ -99,13 +103,27 @@ The Desktop app uses the same Compose Multiplatform UI with adaptations for larg
The Desktop app provides in-app toggles for controlling which notifications are shown β messages, new nodes, and low battery alerts. Access these from **Settings β Notifications** within the app.
+## Built-in Documentation Browser
+
+The Desktop app includes a built-in documentation browser for quick access to help content without leaving the application.
+
+
+
+The browser supports full-text search across all documentation:
+
+
+
+Individual doc pages render with full formatting:
+
+
+
## Building from Source
```bash
git clone https://github.com/meshtastic/Meshtastic-Android.git
cd Meshtastic-Android
git submodule update --init
-./gradlew :desktop:run
+./gradlew :desktopApp:run
```
Requirements:
@@ -114,10 +132,10 @@ Requirements:
## Known Limitations
-- No Bluetooth support
- No OTA firmware updates (use web flasher)
- Some Android-specific features (widgets, specific notification channels) are unavailable
- Performance may vary on low-spec hardware running Compose Desktop
+- BLE bonding is not yet supported on desktop (pairing works without bonding)
## Related Topics
diff --git a/docs/user/discovery.md b/docs/user/discovery.md
index 55c5d8d34d..7a0fba364a 100644
--- a/docs/user/discovery.md
+++ b/docs/user/discovery.md
@@ -96,7 +96,7 @@ The node list itself is a powerful discovery tool when you use its filtering and
### Infrastructure Audit
-- Disable **Exclude infrastructure** to see Router, Repeater, and Router Client nodes.
+- Disable **Exclude infrastructure** to see Router, Repeater, Router Late, and Client Base nodes.
- Check their signal quality and last-heard times to verify your infrastructure nodes are healthy.
See [Nodes](nodes) for full details on filtering and sorting options.
diff --git a/docs/user/firmware.md b/docs/user/firmware.md
index 05fc041694..0b0697ca45 100644
--- a/docs/user/firmware.md
+++ b/docs/user/firmware.md
@@ -78,6 +78,8 @@ If the update appears frozen:
- If truly stuck, power-cycle the radio
- Attempt the update again
+
+
### Device Won't Boot After Update
If your device fails to boot:
diff --git a/docs/user/messages-and-channels.md b/docs/user/messages-and-channels.md
index 80a7195e59..0f8ed31755 100644
--- a/docs/user/messages-and-channels.md
+++ b/docs/user/messages-and-channels.md
@@ -42,6 +42,8 @@ Channels support multiple encryption levels:
3. Configure the channel name and encryption key.
4. Share the channel URL/QR code with others who need access.
+Tapping a channel shows its details and sharing options.
+
## Direct Messages
Direct messages (DMs) are point-to-point encrypted communications between two specific nodes.
@@ -57,8 +59,11 @@ Direct messages (DMs) are point-to-point encrypted communications between two sp
| State | Icon | Meaning |
|-------|------|---------|
| Queued | β³ | Message waiting to be sent |
-| Sent | β | Message transmitted to mesh |
+| En route | β | Delivered to the radio, awaiting acknowledgment |
| Delivered | ββ | Acknowledgment received from recipient |
+| Received | β | Message received from the mesh (incoming) |
+| S&F Routing | π | Store & Forward: message being routed through an S&F node |
+| S&F Confirmed | π | Store & Forward: delivery confirmed via S&F node |
| Error | β | Delivery failed after retries |
### Delivery Errors
@@ -92,9 +97,7 @@ Pre-configured messages for rapid communication:

-The channel list shows each channel with its latest message preview:
-
-
+The channel list shows each channel with its latest message preview.
### Message Bubbles
diff --git a/docs/user/mqtt.md b/docs/user/mqtt.md
index f79bffc3e2..94c4c9db1e 100644
--- a/docs/user/mqtt.md
+++ b/docs/user/mqtt.md
@@ -2,7 +2,7 @@
title: MQTT
nav_order: 11
last_updated: 2026-05-13
-description: Bridge your mesh to the internet β MQTT broker setup, encryption layers, JSON output, and map reporting.
+description: Bridge your mesh to the internet β MQTT broker setup, encryption layers, and map reporting.
aliases:
- mqtt
- internet-bridge
@@ -46,7 +46,7 @@ A gateway node with internet access (WiFi or Ethernet) publishes mesh messages t
| Password | Broker authentication | large4cats |
| Root Topic | Base topic for messages | msh |
| Encryption | Encrypt MQTT payload | Enabled |
-| JSON Output | Publish JSON alongside protobuf | Disabled |
+| ~~JSON Output~~ | β οΈ **Deprecated** β JSON packet support has been removed from firmware; this field is ignored | Disabled |
| TLS | Secure connection to broker | Disabled |
| Map Reporting | Report position to public map | Disabled |
@@ -83,14 +83,13 @@ Configure per-channel which directions are active to control message flow and ai
## Message Formats
-MQTT supports two message formats:
+MQTT uses protobuf message format:
| Format | Description | Use case |
|--------|-------------|----------|
-| **Protobuf** (default) | Binary Meshtastic protobuf encoding | Node-to-node mesh bridging |
-| **JSON** | Human-readable JSON encoding | Home automation, logging, custom integrations |
+| **Protobuf** | Binary Meshtastic protobuf encoding | Node-to-node mesh bridging |
-When **JSON Output** is enabled, the gateway publishes both protobuf and JSON versions of each message to separate topics.
+> β οΈ **Note:** JSON output support was removed from firmware. The `json_enabled` setting is still visible in the app for legacy compatibility but has no effect on current firmware versions.
## Encryption & Privacy
diff --git a/docs/user/nodes.md b/docs/user/nodes.md
index b66a94b6ed..ec5f2ebd9b 100644
--- a/docs/user/nodes.md
+++ b/docs/user/nodes.md
@@ -39,11 +39,13 @@ Nodes can be configured with different roles that affect their mesh behavior:
| Role | Description |
|------|-------------|
| Client | Standard end-user device |
+| Client Base | Treats favorited-node traffic as Router Late priority; all other traffic as Client |
| Client Mute | Receives but doesn't retransmit |
| Client Hidden | Like Client Mute, plus hides from node list |
| Router | Prioritizes message forwarding; stays awake to relay |
-| Router Client | Routes and operates as a client |
-| Repeater | Retransmits only; no user interface |
+| Router Late | Infrastructure node that rebroadcasts once, but only after all other modes (provides supplemental coverage) |
+| ~~Router Client~~ | β οΈ **Deprecated** (removed in firmware 2.3.15) β no longer selectable; use Router or Client instead |
+| ~~Repeater~~ | β οΈ **Deprecated** (removed in firmware 2.7.11) β no longer selectable; use Router instead |
| Tracker | Optimized for position reporting at regular intervals |
| Sensor | Optimized for telemetry reporting |
| TAK | Interoperates with TAK systems (sends/receives CoT) |
@@ -55,9 +57,9 @@ Nodes can be configured with different roles that affect their mesh behavior:
Most users should keep the default **Client** role. Consider a different role when:
- **Router** β You have a node in a fixed, elevated location with reliable power (rooftop, hilltop). Routers stay awake continuously to relay messages for others and are essential for extending mesh coverage. Don't use Router on battery-powered handheld devices.
-- **Router Client** β Like Router, but the device is also used as a personal client. Good for a home base station that you also send messages from.
+- **Router Late** β An infrastructure node that always rebroadcasts packets once but only after all other routing modes have had their turn. Provides supplemental coverage for local clusters without competing with primary routers.
+- **Client Base** β Treats traffic from/to your favorited nodes with Router Late priority (ensuring those messages get extra relay coverage) while handling everything else as a normal Client.
- **Client Mute** β You want to receive mesh traffic but not contribute to relaying. Useful for monitoring-only devices or to reduce congestion in dense areas.
-- **Repeater** β A dedicated relay node with no screen or user interaction. Optimized purely for forwarding; lowest power consumption of the relay roles.
- **Tracker** β An unattended device whose sole purpose is broadcasting its GPS position (e.g., a vehicle, pet, or asset). Sleeps between broadcasts to conserve battery.
- **Sensor** β An unattended device reporting environmental telemetry (temperature, humidity, air quality). Similar power profile to Tracker.
- **TAK / TAK Tracker** β Only needed if interoperating with ATAK/WinTAK systems. See [TAK Integration](tak) for details.
@@ -100,7 +102,7 @@ Type in the search field to filter nodes by name or short name. The filter updat
| **Only online** | Show only nodes heard within the last 15 minutes |
| **Only direct** | Show only nodes with direct (non-relayed) connections |
| **Include unknown** | Show nodes that haven't sent user info yet |
-| **Exclude infrastructure** | Hide infrastructure-role nodes (Router, Repeater, Router Client) |
+| **Exclude infrastructure** | Hide infrastructure-role nodes (Router, Repeater, Router Late, Client Base) |
| **Exclude MQTT** | Hide nodes heard only via MQTT internet bridge |
| **Show ignored** | Show nodes you've previously dismissed or muted |
diff --git a/docs/user/settings-module-admin.md b/docs/user/settings-module-admin.md
index c866ef08f3..5cd31e7a75 100644
--- a/docs/user/settings-module-admin.md
+++ b/docs/user/settings-module-admin.md
@@ -21,6 +21,10 @@ Module settings use a card-based layout with toggle switches, dropdowns, text fi

+
+
+
+
## Module Configuration
### MQTT Module
@@ -34,7 +38,7 @@ Bridges mesh messages to and from an MQTT broker for internet connectivity. This
| Username | Authentication username |
| Password | Authentication password |
| Encryption | Encrypt MQTT payloads |
-| JSON Output | Also publish in JSON format |
+| ~~JSON Output~~ | β οΈ **Deprecated** β JSON support removed from firmware; field is ignored |
| TLS | Use secure connection |
| Root Topic | Base MQTT topic path |
| Map Report | Publish position for public map |
@@ -112,7 +116,7 @@ Pre-configured messages accessible from the device's physical buttons (for radio
| Setting | Description |
|---------|-------------|
-| Enabled | Activate canned messages |
+| ~~Enabled~~ | β οΈ **Deprecated** β current firmware may ignore this toggle |
| Messages | Newline-separated list of messages |
| Send Bell | Play bell sound on send |
| Rotary Encoder | Enable rotary encoder input |
diff --git a/docs/user/settings-radio-user.md b/docs/user/settings-radio-user.md
index 7bed4a4d82..f2bd9e2d8c 100644
--- a/docs/user/settings-radio-user.md
+++ b/docs/user/settings-radio-user.md
@@ -28,8 +28,6 @@ Configure your radio hardware and user identity parameters.
After modifying settings, tap **Save** to write the configuration to your radio. The device may reboot to apply changes.
-
-
## Radio Configuration
### Device Config
@@ -37,8 +35,6 @@ After modifying settings, tap **Save** to write the configuration to your radio.
| Setting | Description | Default |
|---------|-------------|---------|
| Role | Node behavior (Client, Router, etc.) | Client |
-| Serial Output | Enable serial console output | Disabled |
-| Debug Log | Enable verbose debug logging | Disabled |
| Rebroadcast Mode | How the node retransmits messages | All |
| Node Info Broadcast (s) | Interval for broadcasting node info | 10800 |
| Double-tap Button | Action for double-tap button press | Disabled |
@@ -62,11 +58,18 @@ After modifying settings, tap **Save** to write the configuration to your radio.
|--------|-------|-------|-----------|----------|
| Short Turbo | ~1 km | 21.9 kbps | β5 dB | Dense urban with line-of-sight; data-heavy applications |
| Short Fast | ~3 km | 10.9 kbps | β7.5 dB | Urban neighborhoods; buildings within a few blocks |
+| Short Slow | ~5 km | 5.5 kbps | β10 dB | Suburban short-range; moderate building density |
| Medium Fast | ~5 km | 5.5 kbps | β10 dB | Suburban areas; moderate building density |
+| Medium Slow | ~8 km | 1.1 kbps | β12.5 dB | Suburban/rural; moderate range with slower speed |
+| Long Turbo | ~10 km | 4.4 kbps | β10 dB | Similar range to Long Fast but with 500 kHz bandwidth; faster throughput |
| Long Fast | ~10 km | 1.1 kbps | β12.5 dB | **General use (default)** β balanced range and speed |
| Long Moderate | ~20 km | 0.34 kbps | β15 dB | Rural with some terrain; occasional use |
-| Long Slow | ~30 km | 0.18 kbps | β17.5 dB | Sparse rural; maximum reliable range |
-| Very Long Slow | ~40+ km | 0.09 kbps | β20 dB | Extreme range experiments; very slow throughput |
+| Lite Fast | ~5 km | 5.5 kbps | β10 dB | EU 866 MHz SRD band (125 kHz BW); comparable to Medium Fast |
+| Lite Slow | ~10 km | 1.1 kbps | β12.5 dB | EU 866 MHz SRD band (125 kHz BW); comparable to Long Fast |
+| Narrow Fast | ~5 km | 2.7 kbps | β10 dB | EU 868 MHz band (62.5 kHz BW); avoids interference with other devices |
+| Narrow Slow | ~10 km | 1.1 kbps | β12.5 dB | EU 868 MHz band (62.5 kHz BW); comparable to Long Fast |
+| ~~Long Slow~~ | ~30 km | 0.18 kbps | β17.5 dB | β οΈ **Deprecated** β still selectable but may be removed in a future firmware release |
+| ~~Very Long Slow~~ | ~40+ km | 0.09 kbps | β20 dB | β οΈ **Deprecated** β still selectable but may be removed in a future firmware release |
#### Choosing a Modem Preset
@@ -78,8 +81,9 @@ The modem preset controls the fundamental tradeoff between **range** and **data
**Practical guidance:**
- **Urban mesh (many nodes, short distances):** Use **Long Fast** (default) or **Short Fast**. Higher speed means less airtime congestion when many nodes share the channel.
-- **Rural/sparse mesh (few nodes, long distances):** Use **Long Moderate** or **Long Slow**. Range matters more than speed when nodes are far apart.
-- **Fixed infrastructure links:** Use **Short Turbo** for dedicated point-to-point links with good antennas and line-of-sight.
+- **Rural/sparse mesh (few nodes, long distances):** Use **Long Moderate**. Range matters more than speed when nodes are far apart.
+- **EU 866/868 MHz regulatory compliance:** Use **Lite Fast**, **Lite Slow**, **Narrow Fast**, or **Narrow Slow** β these are optimized for the EU SRD/868 MHz bands with narrower bandwidths.
+- **Fixed infrastructure links:** Use **Short Turbo** or **Long Turbo** for dedicated point-to-point links with good antennas and line-of-sight.
- **Mixed environments:** Stick with **Long Fast** β it's the community default and ensures compatibility with others in your area.
> β οΈ **Important:** All nodes on the same channel **must** use the same modem preset. Nodes with mismatched presets cannot communicate even if they share the same frequency and encryption key.
@@ -92,9 +96,9 @@ The modem preset controls the fundamental tradeoff between **range** and **data
|---------|-------------|
| Screen Timeout | Time before display sleeps |
| Display Units | Metric or Imperial |
-| GPS Format | DMS, Decimal, UTM, MGRS, OLC |
| OLED Type | Auto, SSD1306, SH1106, SH1107 |
-| Compass North | True North or Magnetic North |
+| Compass Orientation | Rotation offset for compass display (0Β°, 90Β°, 180Β°, 270Β°) |
+| ~~Compass North~~ | β οΈ **Deprecated** β replaced by Compass Orientation; still visible in older firmware |
### Position Config
@@ -126,6 +130,8 @@ The modem preset controls the fundamental tradeoff between **range** and **data
| NTP Server | Time synchronization server |
| Syslog Server | Remote logging server |
+
+
### Bluetooth Config
| Setting | Description |
@@ -142,8 +148,12 @@ The modem preset controls the fundamental tradeoff between **range** and **data
| Admin Key | Key for remote administration |
| Private Key | Your node's private key (handle securely) |
| Admin Channel Enabled | Allow admin commands via channel |
+| Debug Log | Output live debug logging over serial/bluetooth |
+| Serial Enabled | Enable serial console access (moved from Device Config) |
| Managed Mode | Restrict non-admin channel changes |
+
+
Settings use standard preference controls β dropdowns, toggles, and sliders:
| Control | Screenshot |
diff --git a/docs/user/signal-meter.md b/docs/user/signal-meter.md
index 3f573ca4d5..ce2d0a66d9 100644
--- a/docs/user/signal-meter.md
+++ b/docs/user/signal-meter.md
@@ -52,10 +52,10 @@ Here is exactly how the app decides how many bars (or what color) to show you:
| Level | Bars | Criteria | Meaning |
|-------|------|----------|---------|
-| Good | 3 | RSSI better than `-115 dBm` **AND** SNR above the baseline limit for your preset | Signal is both loud and clear β healthy connection. |
-| Fair | 2 | Falls between Good and Bad | Signal getting quieter or noisier, but the radio understands the message fine. |
-| Bad | 1 | RSSI drops to `-120 dBm` or worse, **OR** SNR within `5.5 dB` of your preset's absolute breaking point | Barely hanging on β at the edge of range or heavy interference. |
-| None | 0 | RSSI worse than `-126 dBm` **AND** SNR has fallen `7.5 dB` below the ideal limit | Transmission completely buried in static. |
+| Good | 3 | RSSI better than `-115 dBm` **AND** SNR better than `-7 dB` | Signal is both loud and clear β healthy connection. |
+| Fair | 2 | RSSI better than `-126 dBm` with good SNR, **OR** SNR better than `-15 dB` with good RSSI | Signal getting quieter or noisier, but still decodable. |
+| Bad | 1 | Falls between Fair and None thresholds | At the edge of range or experiencing interference. |
+| None | 0 | RSSI worse than `-126 dBm` **AND** SNR worse than `-15 dB` | Transmission completely buried in noise. |
---
Served by rngit 1.5.2 - Generated in 0.3s